#include "stdafx.h"
#include "SocketsDaemon.h"

using namespace Adaptive::Network::Sockets;

/*--------------------------------------------------------------------------------
* Constructor / Dispose / Destructor Methods
*------------------------------------------------------------------------------*/
#pragma region Constructor / Dispose / Destructor Methods
SocketsDaemon::SocketsDaemon() : DisposableObjectBase()
{
	Win32Socket::InitializeWinsock();
	_listenerSocket = nullptr;
	_connectionManager = gcnew SocketsConnectionManager();
	_connectionPollThread = nullptr;
	_localEndPoint = nullptr;
	_executeFlag = false;
	_maxConnections = Int32::MaxValue;
}
SocketsDaemon::~SocketsDaemon()
{
	if (!Disposed)
	{
		ShutdownActivity(false);

		if (_listenerSocket != nullptr)
			delete(_listenerSocket);

		if (_connectionManager != nullptr)
		{
			_connectionManager->CloseAll();
			delete(_connectionManager);
		}
		
		if (_connectionPollThread != nullptr)
			delete(_connectionPollThread);
		if (_localEndPoint != nullptr)
			delete(_localEndPoint);

		Win32Socket::ShutdownWinsock();
	}
	_listenerSocket = nullptr;
	_connectionManager = nullptr;
	_connectionPollThread = nullptr;
	_localEndPoint = nullptr;
	_executeFlag = false;
	_maxConnections = 0;
}
SocketsDaemon::!SocketsDaemon()
{
	_listenerSocket = nullptr;
	_connectionManager = nullptr;
	_connectionPollThread = nullptr;
	_localEndPoint = nullptr;
	_executeFlag = false;
	_maxConnections = 0;
}
#pragma endregion

/*--------------------------------------------------------------------------------
* Public Properties
*------------------------------------------------------------------------------*/
#pragma region Public Properties

SocketsConnectionManager^ SocketsDaemon::ConnectionManager::get()
{
	return _connectionManager;
}
String^ SocketsDaemon::Id::get()
{
	if (_listenerSocket != nullptr)
		return _listenerSocket->DangerousGetHandle().ToInt32().ToString();
	else
		return nullptr;
}
bool SocketsDaemon::IsListening::get()
{
	return (_listenerSocket != nullptr);
}
int SocketsDaemon::MaxConnections::get()
{
	return _maxConnections;
}
void SocketsDaemon::MaxConnections::set(int value)
{
	Thread::BeginCriticalRegion();

	_maxConnections = value;

	Thread::EndCriticalRegion();
}
IPEndPoint^ SocketsDaemon::LocalEndPoint::get()
{
	return _localEndPoint;
}
void SocketsDaemon::LocalEndPoint::set(IPEndPoint^ endPoint)
{
	if (_listenerSocket == nullptr)
	{
		_localEndPoint = endPoint;
	}
}
#pragma endregion

/*--------------------------------------------------------------------------------
* Public Methods / Functions
*------------------------------------------------------------------------------*/
#pragma region Public Methods / Functions
bool SocketsDaemon::Listen(IPEndPoint^ endPoint)
{
	bool returnValue = false;

	if (_listenerSocket == nullptr)
	{
		if (endPoint == nullptr)
			throw gcnew ArgumentNullException("endPoint");
		else
		{
			_localEndPoint = endPoint;
			returnValue = Listen();
		}
	}
	return returnValue;
}
bool SocketsDaemon::Listen()
{
	bool returnValue = false;

	if (_listenerSocket == nullptr)
	{
		//Bind to the local end point.
		if (_localEndPoint == nullptr)
		{
			Exception^ ex = gcnew Exception("Cannot instantiate listener on null address!");
			OnSocketException(gcnew SocketErrorEventArgs(ex));
		}
		else
		{
			try
			{
				_listenerSocket = Win32Socket::InitSocket(AddressFamily::InterNetwork, SocketType::Stream, ProtocolType::Tcp);
			}
			catch(Exception^ ex)
			{
				OnSocketException(gcnew SocketErrorEventArgs(ex));
				_listenerSocket = nullptr;
			}
			if (_listenerSocket != nullptr)
			{
				try
				{
					returnValue = Win32Socket::Bind(_listenerSocket, _localEndPoint->Address, (unsigned short)_localEndPoint->Port);
				}
				catch(Exception^ ex)
				{
					OnSocketException(gcnew SocketErrorEventArgs(ex));
					returnValue = false;
				}
				if (returnValue)
				{
					returnValue = Win32Socket::Listen(_listenerSocket, _maxConnections);
				}
				
				if (returnValue)
				{
					LaunchPollingThread();
				}
				else
				{
					_listenerSocket->Close();
					delete(_listenerSocket);
					_listenerSocket = nullptr;
					returnValue = false;
				}
			}
		}
	}
	else
		returnValue = true;

	return returnValue;
}
void SocketsDaemon::StopListening()
{
	
	TerminatePollingThread();
	if (_listenerSocket != nullptr)
	{
		Thread::BeginCriticalRegion();

		_listenerSocket->Close();
		delete(_listenerSocket);
		_listenerSocket = nullptr;
		_executeFlag = false;

		Thread::EndCriticalRegion();
	}
}
void SocketsDaemon::ShutdownActivity(bool closeGracefully)
{
	Thread::BeginCriticalRegion();

	if (closeGracefully)
		StopListening();
	else
	{
		if ((_connectionPollThread != nullptr) && (_connectionPollThread->IsAlive))
		{
			try
			{
				_connectionPollThread->Abort();
			}
			catch(...)
			{
			}
			delete(_connectionPollThread);
			_connectionPollThread=nullptr;
			_executeFlag = false;
		}
	}

	if (closeGracefully)
	{
		_connectionManager->CloseAll();
	}
	else
	{
		_connectionManager->AsyncCloseAll();
		delete(_connectionManager);
		_connectionManager = gcnew SocketsConnectionManager();
	}

	Thread::EndCriticalRegion();
}
#pragma endregion

/*--------------------------------------------------------------------------------
* Event Methods
*------------------------------------------------------------------------------*/
#pragma region Event Methods
void SocketsDaemon::OnConnectionRequest(ConnectionRequestEventArgs^ e)
{
	ConnectionRequest(this, e);
}
void SocketsDaemon::OnConnection(ConnectionEventArgs^ e)
{
	Connection(this, e);
}
void SocketsDaemon::OnSocketException(SocketErrorEventArgs^ e)
{
	SocketException(this, e);
}
#pragma endregion

/*--------------------------------------------------------------------------------
* Private Methods / Functions
*------------------------------------------------------------------------------*/
#pragma region Private Methods / Functions
void SocketsDaemon::LaunchPollingThread()
{
	if ((_connectionPollThread == nullptr) && (_listenerSocket != nullptr))
	{
		_connectionPollThread = gcnew Thread(gcnew ThreadStart(this, &SocketsDaemon::ExecutePollingThread));
		_connectionPollThread->IsBackground = true;
		_connectionPollThread->Priority = ThreadPriority::AboveNormal;
		_executeFlag = true;
		_connectionPollThread->Start();
	}
}
void SocketsDaemon::ExecutePollingThread()
{
	SafeSocketHandle^ newSocketHandle = nullptr;
	bool readable = false;

	while ((_executeFlag) && (_listenerSocket != nullptr))
	{
		try
		{
			readable = Win32Socket::SelectSocketReadble(_listenerSocket);
		}
		catch(Exception^ ex)
		{
			OnSocketException(gcnew SocketErrorEventArgs(ex));
			readable = false;
		}

		if (readable)
		{
			if (_connectionManager->Count < _maxConnections)
			{
				newSocketHandle = Win32Socket::Accept(_listenerSocket);
				if (newSocketHandle != nullptr)
					LaunchConnectionRequestThread(newSocketHandle);
				newSocketHandle = nullptr;
			}
		}
		Thread::Sleep(10);
	}

}
void SocketsDaemon::TerminatePollingThread()
{
	clock_t start = 0;
	clock_t end = 0;

	_executeFlag = false;

	start = clock();
	do
	{
		Sleep(10);
		end = clock();
	} while ((_connectionPollThread != nullptr) && (_connectionPollThread->IsAlive) && (end-start < 30000));

	if ((_connectionPollThread != nullptr) && (_connectionPollThread->IsAlive))
	{
		try
		{
			_connectionPollThread->Abort();
		}
		catch(...)
		{
		}
		finally
		{
			delete(_connectionPollThread);
			_connectionPollThread = nullptr;
		}
	}
}
void SocketsDaemon::AsyncTerminatePollingThread()
{
	Thread^ thread = nullptr;

	thread = gcnew Thread(gcnew ThreadStart(this, &SocketsDaemon::TerminatePollingThread));
	thread->IsBackground = true;
	thread->Start();
}
void SocketsDaemon::LaunchConnectionRequestThread(SafeSocketHandle^ newSocketInstance)
{
	ParameterizedThreadStart^ functionptr = nullptr;
	Thread^ thread = nullptr;

	functionptr = gcnew ParameterizedThreadStart(this, &SocketsDaemon::ExecuteConnectionRequestThread);
	thread = gcnew Thread(functionptr);
	thread->IsBackground = true;
	thread->Start(newSocketInstance);
}
void SocketsDaemon::ExecuteConnectionRequestThread(Object^ newSocketInstance)
{
	ConnectionRequestEventArgs^ newArgs = nullptr;
	SocketsConnection^ newConnection = nullptr;

	newConnection = gcnew SocketsConnection((SafeSocketHandle^)newSocketInstance);
	newArgs = gcnew ConnectionRequestEventArgs(newConnection);
	newArgs->Allow = true;
	OnConnectionRequest(newArgs);

	if (newArgs->Allow)
	{
		Thread::BeginCriticalRegion();
		_connectionManager->Add(newConnection);
		Thread::EndCriticalRegion();

		OnConnection(gcnew ConnectionEventArgs(newConnection));
	}
	else
	{
		newConnection->Close();
		delete(newConnection);
		newConnection = nullptr;
		((SafeSocketHandle^)newSocketInstance)->Close();
		delete(newSocketInstance);
		newSocketInstance=nullptr;
	}

	delete(newArgs);
}
#pragma endregion
